package com.snow.datasource.advisor;

import cn.hutool.core.util.ObjectUtil;
import com.snow.datasource.annoation.Snow;
import lombok.NonNull;
import org.aopalliance.aop.Advice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.*;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.core.annotation.AnnotationUtils;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Description
 * @Author fanxl
 * @Date 2020/12/17
 **/
public class SnowDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor  implements Serializable {

    public  static Logger log = LoggerFactory.getLogger(SnowDataSourceAnnotationAdvisor.class);

    private static final long serialVersionUID = 1L;

    private final Advice advice;

    private final Pointcut pointcut;

    public SnowDataSourceAnnotationAdvisor(@NonNull SnowDataSourceAnnotationInterceptor snowDataSourceAnnotationInterceptor) {
        this.advice = snowDataSourceAnnotationInterceptor;
        this.pointcut = buildPointcut();
    }

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    public Advice getAdvice() {
        return this.advice;
    }

    private Pointcut buildPointcut() {
        Pointcut cpc = new AnnotationMatchingPointcut(Snow.class, true);
        Pointcut mpc = new AnnotationMethodPoint(Snow.class);
        return new ComposablePointcut(cpc).union(mpc);
    }

    /**
     * In order to be compatible with the spring lower than 5.0
     */
    private static class AnnotationMethodPoint implements Pointcut {

        private final Class<? extends Annotation> annotationType;

        public AnnotationMethodPoint(Class<? extends Annotation> annotationType) {
            this.annotationType = annotationType;
        }

        @Override
        public ClassFilter getClassFilter() {
            return ClassFilter.TRUE;
        }

        @Override
        public MethodMatcher getMethodMatcher() {
            return new AnnotationMethodMatcher(annotationType);
        }

        private static class AnnotationMethodMatcher extends StaticMethodMatcher {
            private final Class<? extends Annotation> annotationType;

            public AnnotationMethodMatcher(Class<? extends Annotation> annotationType) {
                this.annotationType = annotationType;
            }

            @Override
            public boolean matches(Method method, Class<?> targetClass) {
            /*    log.info(method.getName());*/
                if (matchesMethod(method)) {
                    return true;
                }

                // 代理类不加注解
                if (Proxy.isProxyClass(targetClass)) {
                    return false;
                }
                // The method may be on an interface, so let's check on the target class as well.
                Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
                return (specificMethod != method && matchesMethod(specificMethod));
            }

            private boolean matchesMethod(Method method) {
                Object annotation = AnnotationUtils.getAnnotation(method,this.annotationType);
                if(ObjectUtil.isNull(annotation)){
                    return false;
                }
                return true;
            }
        }
    }
}
